﻿using FileTransfer.Except;
using FileTransfer.Model;
using FluentFTP;
using HAF.LogUtility;
using HAF.LogUtility.Model;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;

namespace FileTransfer.Assistantor
{
    /// <summary>
    /// FluentFtp下载组件
    /// </summary>
    public class FluentFtpHelper : IDisposable
    {
        #region 运行参数
        Guid currentFileId = Guid.Empty;
        /// <summary>  
        /// FTP服务器IP地址
        /// </summary>  
        public string Host { get; private set; }
        /// <summary>
        /// FTP服务器端口
        /// </summary>
        public int Port { get; private set; }
        /// <summary>  
        /// FTP服务器默认目录
        /// </summary>  
        public string RemotePath { get; private set; }
        /// <summary>  
        /// FTP服务器登录用户名
        /// </summary>  
        public string UserID { get; private set; }
        /// <summary>  
        /// FTP服务器登录密码
        /// </summary>  
        public string Password { get; private set; }
        /// <summary>
        /// FTP访问编码
        /// </summary>
        public string Encoder { get; private set; }
        /// <summary>
        /// 输出日志文件名
        /// </summary>
        public string logName { get; set; }
        #endregion 运行参数

        private FtpClient _ftpClient;

        #region 外部扩展事件
        /// <summary>
        /// 文件下载消息输出
        /// </summary>
        public Action<String> DownloadMessageEvent;
        /// <summary>
        /// 文件下载进度输出
        /// </summary>
        public Action<string, int> DownloadProcessChangeEvent;
        /// <summary>
        /// 文件下载状态输出
        /// </summary>
        public Action<string, long, long, long, long> DownloadChangeEvent;
        /// <summary>
        /// 文件下载完成事件输出
        /// </summary>
        public Action<string, int> DownloadOverEvent;
        /// <summary>
        /// 文件上传消息输出
        /// </summary>
        public event Action<double> UploadProgressChangeEvent;
        #endregion 外部扩展事件

        public FluentFtpHelper(SiteConfig siteConfig) : this(siteConfig.IP, siteConfig.Port, "/", siteConfig.UserId, siteConfig.Password, siteConfig.Encoding, siteConfig.TimeOut) {}
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="ftpServerIP">地址</param>
        /// <param name="ftpPort">端口</param>
        /// <param name="ftpRemotePath">远端文件全路径</param>
        /// <param name="ftpUserID">账号</param>
        /// <param name="ftpPassword">密码</param>
        /// <param name="ftpEncoding">字符集</param>
        /// <param name="timeOut">超时时长</param>
        /// <exception cref="TransferException">异常抛出</exception>
        public FluentFtpHelper(string ftpServerIP, int ftpPort, string ftpRemotePath, string ftpUserID, string ftpPassword, string ftpEncoding, int timeOut)
        {
            this.currentFileId = Guid.NewGuid();
            this.Host = ftpServerIP;
            this.Port = ftpPort;
            this.RemotePath = ftpRemotePath;
            this.UserID = ftpUserID;
            this.Password = ftpPassword;
            this.Encoder = string.IsNullOrEmpty(ftpEncoding) ? "utf8" : ftpEncoding;

            if (string.IsNullOrEmpty(ftpRemotePath))
            {
                this.RemotePath = "ftp://" + ftpServerIP + ":" + ftpPort + "/";
            }
            else
            {
                this.RemotePath = "ftp://" + ftpServerIP + ":" + ftpPort + "/" + ftpRemotePath + "/";
            }
            try
            {
                _ftpClient = new FtpClient(Host);
                _ftpClient.Port = Port;
                _ftpClient.Credentials = new NetworkCredential(UserID, Password);
                _ftpClient.Encoding = this.Encoder.ToLower().Equals("utf8") ? Encoding.UTF8 : Encoding.Default;
                //设置30分钟的超时时间 30 * 60 * 1000; 4K视频太大，下载时间很长
                //_ftpClient.ConnectTimeout = timeOut == 0 ? 10000 : timeOut;
                _ftpClient.Config.ConnectTimeout = timeOut == 0 ? 10000 : timeOut; ;
                //保持链接
                _ftpClient.Config.SocketKeepAlive = true;
                
            }
            catch (Exception ex)
            {
                WriteErrorLog(ex.ToString());
                throw new TransferException((int)TransferCode.AuthFailed, ex.ToString());
            }
            
        }

        /// <summary>
        /// 获取字节长度字符
        /// </summary>
        /// <param name="resLength">字节数</param>
        /// <returns></returns>
        private string GetLengthStr(long resLength)
        {
            if (resLength >= (long)1024 * 1024 * 1024)
            {
                return string.Format("{0}GB ({1}bytes)", Math.Round((double)resLength / (1024 * 1024 * 1024), 2), resLength);
            }
            else if (resLength >= (long)1024 * 1024)
            {
                return string.Format("{0}MB ({1}bytes)", Math.Round((double)resLength / (1024 * 1024), 2), resLength);
            }
            else
            {
                return string.Format("{0}KB ({1}bytes)", Math.Round((double)resLength / (1024), 2), resLength);
            }
        }

        private string GetTimeStr(long downTime)
        {
            if (downTime >= (long)60 * 60 * 1000)
            {
                return string.Format("{0}小时 ({1}毫秒)", Math.Round((double)downTime / (1000 * 60 * 60), 2), downTime);
            }
            else if (downTime >= (long)60 * 1000)
            {
                return string.Format("{0}分钟 ({1}毫秒)", Math.Round((double)downTime / 1000 / 60), downTime);
            }
            else
            {
                return string.Format("{0}毫秒", downTime);
            }
        }

        /// <summary>
        /// 文件下载
        /// </summary>
        /// <param name="saveFile">本地存储文件全路径</param>
        /// <param name="remoteFile">远端文件全路径</param>
        /// <param name="threadId">线程编号</param>
        /// <returns></returns>
        public ResponseLog DownFile(string saveFile, string remoteFile, string threadId = "")
        {
            WriteInfoLog("进入FluentFtp下载");
            ResponseLog result = new ResponseLog() { Code = (int)TransferCode.Success, Message = "成功" };

            if (_ftpClient == null || _ftpClient.IsDisposed)
            {
                return new ResponseLog() { Code = (int)TransferCode.Error, Message = "FTP未准备好" };
            }
            WriteDebugLog("检查本地下载目录");
            saveFile = saveFile.Replace("/", "\\");
            //本地文件存储目录
            string saveDir = saveFile.Substring(0, saveFile.LastIndexOf(@"\"));
            if (!Directory.Exists(saveDir))
            {
                Directory.CreateDirectory(saveDir);
                WriteInfoLog(String.Format("创建下载目录{0}", saveDir));
            }
            
            try
            {
                WriteDebugLog(string.Format("准备下载 Host:{0} Port:{1} UserID:{2} Password:{3}", Host, Port, UserID, Password));
                Stopwatch _stopwathc = new Stopwatch();
                _stopwathc.Start();
                WriteDebugLog("检查FTP链路");
                //if (ftpClient.IsConnected)
                //{
                //    ftpClient.Disconnect();
                //}

                if (!_ftpClient.IsConnected)
                {
                    _ftpClient.Connect();
                }
                //using (FtpClient client = new FtpClient(Host, Port, new NetworkCredential(UserID, Password)))
                {
                    //, Port, UserID, Password
                    WriteDebugLog("检查远端文件是否存在");
                    //第一步：判断文件是否存在
                    if (_ftpClient.FileExists(remoteFile))
                    {
                        //第二步：获取文件大小（用于进度展示）
                        long dataLength = _ftpClient.GetFileSize(remoteFile);
                        WriteInfoLog(String.Format("到FTP服务端获取介质文件{0}大小为{1} {2}字节", remoteFile, GetLengthStr(dataLength), dataLength));
                        if (dataLength > 1)
                        {
                            WriteInfoLog(String.Format("开始下载文件ftp://{0}:{1}/{2}到{3}", Host, Port, remoteFile, saveFile));
                            bool isFileExist = false;
                            if (File.Exists(saveFile))
                            {
                                isFileExist = true;
                                FileInfo info = new FileInfo(saveFile);
                                WriteInfoLog(string.Format("本地文件{0}已存在，目前大小为{1}", saveFile, info.Length));
                                if (info.Length.Equals(dataLength))
                                {
                                    WriteInfoLog(string.Format("经过比对，文件{0}已经正常下载完成，不用重复下载", remoteFile));
                                    return result;
                                }
                            }

                            //小于1M的文件，直接下载
                            if (dataLength < 1024 * 1024)
                            {
                                //小文件下载
                                FtpStatus ftpStatus = _ftpClient.DownloadFile(saveFile, remoteFile);
                                if (ftpStatus == FtpStatus.Success)
                                {
                                    WriteInfoLog(string.Format("文件{0}通过小文件下载方式成功！", remoteFile));
                                }
                                else
                                {
                                    WriteErrorLog(string.Format("文件{0}通过小文件下载方式失败{1}！", remoteFile, ftpStatus));
                                    result.Code = (int)ftpStatus;
                                    result.Message = "不知道为啥失败";
                                }
                            }
                            else
                            {
                                //超过1M的文件下载
                                using (Stream ftpStream = _ftpClient.OpenRead(remoteFile))
                                {
                                    if (ftpStream.Length > 0)
                                    {
                                        //创建本地文件
                                        using (FileStream downStream = new FileStream(saveFile, isFileExist ? FileMode.OpenOrCreate : FileMode.Create))
                                        {

                                            //第三步：创建下载链路
                                            {
                                                FlowAssistantor timer = new FlowAssistantor();
                                                //int _cacheSize = 0;
                                                ////long cl = response1.ContentLength;
                                                ////WriteFtpLog(String.Format("文件{0}大小为{1} {2}", downloadFileName, cl, dataLength));
                                                //int bufferSize = 2048; //2K Byte
                                                int bufferSize = 1 * 1024 * 1024; //1M Byte
                                                //int bufferSize = _cacheSize; //1M Byte
                                                //WriteInfoLog(string.Format("下载文件流缓存大小为{0}", bufferSize));
                                                long readCount = 0; //每次读取长度 注意用long，不要用int，否则会溢出
                                                int readLength = 0;
                                                byte[] buffer = new byte[bufferSize];
                                                //var output = new byte[bufferSize];
                                                //MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider();
                                                //md5Provider.Initialize();
                                                while ((readLength = ftpStream.Read(buffer, 0, bufferSize)) > 0)
                                                {
                                                    //写盘
                                                    downStream.Write(buffer, 0, readLength);
                                                    //在这里可考虑进行MD5计算
                                                    //md5Provider.TransformBlock(buffer, 0, Convert.ToInt32(readLength), output, 0);
                                                    //将缓存数据写入实体文件
                                                    downStream.Flush();
                                                    //readCount = ftpStream.Read(buffer, 0, bufferSize);
                                                    //已经读取的数量累加
                                                    readCount += readLength;
                                                    timer.ReadSize = readCount;
                                                    //runThread.ReadSize = readCount;
                                                    //runThread.WriteSize = downStream.Position;
                                                    //最大、最小速度判定
                                                    //runThread.MaxRate = timer.FlowSize > 0 & timer.FlowSize > runThread.MaxRate ? timer.FlowSize : runThread.MaxRate;
                                                    //runThread.MinRate = timer.FlowSize > 0 & timer.FlowSize < runThread.MinRate ? timer.FlowSize : runThread.MinRate;
                                                    //计算已经读取的数据百分比
                                                    int percentage = (int)(readCount * 100 / dataLength);
                                                    UpdateProcess(threadId, percentage);
                                                    ReportProcess(threadId, dataLength, readCount, downStream.Position, timer.FlowSize);
                                                }
                                                ////完成最后计算，必须调用(由于上一部循环已经完成所有运算，所以调用此方法时后面的两个参数都为0)
                                                //md5Provider.TransformFinalBlock(buffer, 0, 0);
                                                //string md5Str = BitConverter.ToString(md5Provider.Hash).Replace("-", "");
                                                //WriteInfoLog(String.Format("文件{0}计算后的MD5校验值为{1}", remoteFileName, md5Str));
                                                //md5Provider.Clear();
                                                //downStream.Flush(); //将缓存数据写入实体文件
                                                //result.Sign = md5Str;
                                            }
                                        }
                                    }
                                    
                                }
                                
                            }
                        }
                        else
                        {
                            WriteErrorLog(string.Format("文件{0}在FTP服务器{1}上大小不正常", remoteFile, Host));
                        }
                        //byte[] outBuffs;
                        //bool flag = client.Download(out outBuffs, remoteFile);
                    }
                    else
                    {
                        string putmsg = string.Format("文件{0}在FTP服务器{1}上不存在", remoteFile, Host);
                        WriteErrorLog(putmsg);
                        throw new TransferException((int)TransferCode.FileNotFound, putmsg);
                    }
                }
                _stopwathc.Stop();
                FtpReply reply = _ftpClient.LastReply;
                WriteDebugLog(string.Format("下载操作完成，{0}  {1}", reply.Code, reply.Message));

                WriteInfoLog(String.Format("[{0}]文件{1}下载到{2}完成，共耗时{3}毫秒！", reply.Code, Host + remoteFile, saveFile,
                    _stopwathc.ElapsedMilliseconds));
                //WriteInfoLog(String.Format("[{0}]文件{1}下载到{2}完成，最大下载速率为{3}/s，最小下载速率为{4}/s，共耗时{5}！", EnumFTPServiceCode.Success,
                //    remoteFileName, saveFileName, GetLengthStr(runThread.MaxRate), GetLengthStr(runThread.MinRate),
                //    _stopwathc.Elapsed.TotalSeconds > 60 ? _stopwathc.Elapsed.TotalMinutes.ToString() + "分" : _stopwathc.Elapsed.TotalSeconds.ToString() + "秒"));
                return result;
            }
            catch (Exception ex)
            {
                WriteErrorLog(ex.ToString());
                throw new TransferException((int)TransferCode.DownErr, ex.ToString());
            }
            finally
            {
                _ftpClient.Disconnect();
            }
            
            
        }

        /// <summary>
        /// 批量下载文件
        /// </summary>
        /// <param name="remoteFiles">待下载文件集合</param>
        /// <returns></returns>
        public ResponseLog DownPatchFiles(List<FileStruct> remoteFiles)
        {
            ResponseLog result = new ResponseLog() { Code = (int)TransferCode.Success, Message = "成功" };

            try
            {
                WriteDebugLog(string.Format("待访问FTP服务器信息 Host:{0} Port:{1} UserID:{2} Password:{3}", Host, Port, UserID, Password));
                Stopwatch _stopwathc = new Stopwatch();
                _stopwathc.Start();

                foreach (var item in remoteFiles)
                {
                    if (item.BEnable)
                    {
                        string savePath = Path.GetDirectoryName(item.LocalPath);
                        //本地文件存储目录
                        if (!Directory.Exists(savePath))
                        {
                            Directory.CreateDirectory(savePath);
                            WriteInfoLog(String.Format("创建下载目录{0}", savePath));
                        }

                        string saveFile = item.LocalPath; // Path.Combine(savePath, Path.GetFileName(item.RemotePath));
                        ResponseLog response = DownFile(saveFile, item.RemotePath, item.ID);
                        ResponseDownOver(item.ID, response.Code);

                        Thread.Sleep(4000);
                    }
                    else
                    {
                        string errInfo = string.Format("当前介质{0}根据采集规则不予下载", item.RemotePath);
                        WriteDebugLog(errInfo);
                        throw new TransferException((int)TransferCode.NotAllowDownErr, errInfo);
                    }
                }

                _stopwathc.Stop();
                FtpReply reply = _ftpClient.LastReply;
                WriteDebugLog(string.Format("下载操作完成，{0}  {1}", reply.Code, reply.Message));

                //WriteInfoLog(String.Format("[{0}]文件{1}下载到{2}完成，共耗时{3}毫秒！", reply.Code, Host + remoteFile, saveFile,
                //    _stopwathc.ElapsedMilliseconds));
                //WriteInfoLog(String.Format("[{0}]文件{1}下载到{2}完成，最大下载速率为{3}/s，最小下载速率为{4}/s，共耗时{5}！", EnumFTPServiceCode.Success,
                //    remoteFileName, saveFileName, GetLengthStr(runThread.MaxRate), GetLengthStr(runThread.MinRate),
                //    _stopwathc.Elapsed.TotalSeconds > 60 ? _stopwathc.Elapsed.TotalMinutes.ToString() + "分" : _stopwathc.Elapsed.TotalSeconds.ToString() + "秒"));
                return result;
            }
            catch (Exception ex)
            {
                WriteErrorLog(ex.ToString());
                throw new TransferException((int)TransferCode.DownErr, ex.ToString());
            }
        }

        /// <summary>
        /// 获取目录下所有数据
        /// </summary>
        /// <param name="remoteDirectorie">远端目录</param>
        /// <param name="isSub">是否包含子目录 默认包含子目录</param>
        /// <returns></returns>
        public List<FileStruct> GetDirectorieFile(string remoteDirectorie, bool isSub = true)
        {
            WriteInfoLog("进入FluentFtp目录下载线程");
            ResponseLog result = new ResponseLog() { Code = (int)TransferCode.Success, Message = "成功" };

            if (_ftpClient == null || _ftpClient.IsDisposed)
            {
                //return new ResponseLog() { Code = (int)EnumFTPServiceCode.Error, Message = "FTP未准备好" };
                return null;
            }
            WriteDebugLog("检查本地下载目录");

            FtpListOption option = isSub ? FtpListOption.Recursive : FtpListOption.ForceList;
            var fileList = new List<FileStruct>();

            try
            {
                fileList.AddRange(ListAllFiles(remoteDirectorie));
                //DownFiles(saveDir, fileList);
            }
            catch (Exception ex)
            {
                WriteErrorLog(ex.ToString());
            }

            return fileList;
        }

        /// <summary>
        /// 采集指定目录下所有数据
        /// </summary>
        /// <param name="rootDir">检索目录</param>
        /// <param name="isSub">是否包含子目录</param>
        /// <returns></returns>
        public List<FileStruct> ListAllFiles(string rootDir, bool isSub = false, string ignoreDir = "")
        {
            if (_ftpClient == null || _ftpClient.IsDisposed)
            {
                return null;
            }

            var fileList = new List<FileStruct>();
            try
            {
                FtpListItem[] listItem = _ftpClient.GetListing(rootDir, FtpListOption.ForceList);
                foreach (var item in listItem)
                {
                    switch (item.Type)
                    {
                        case FtpObjectType.Directory:
                            if (isSub)
                            {
                                fileList.AddRange(ListAllFiles(item.FullName, isSub));
                            }
                            break;
                        case FtpObjectType.File:
                            string tmpExtName = Path.GetExtension(item.FullName);
                            fileList.Add(new FileStruct()
                            {
                                ID = Guid.NewGuid().ToString("N"),
                                IsDirectory = false,
                                CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                                Name = item.Name,
                                FileExtName = tmpExtName,
                                Size = item.Size,
                                RemotePath = item.FullName
                            });
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                WriteErrorLog(ex.ToString());
            }

            return fileList;
        }

        #region 输出日志
        private void WriteInfoLog(string msg)
        {
            if (DownloadMessageEvent != null)
            {
                DownloadMessageEvent("[INFO] " + msg);
            }
            else
            {
                LogHelper.WriteInfoLogEx("FtpManager", msg);
            }
            //FileDownloadProgressChangeEvent?.Invoke(msg);
        }

        private void WriteDebugLog(string msg)
        {
            if (DownloadMessageEvent != null)
            {
                DownloadMessageEvent("[DEBUG] " + msg);
            }
            else
            {
                LogHelper.WriteErrorLogEx("FtpManager", msg);
            }
        }

        private void WriteErrorLog(string msg)
        {
            if (DownloadMessageEvent != null)
            {
                DownloadMessageEvent("[ERROR] " + msg);
            }
            else
            {
                LogHelper.WriteErrorLogEx("FtpManager", msg);
            }
        }

        /// <summary>
        /// 更新进度条
        /// </summary>
        /// <param name="progress"></param>
        private void UpdateProcess(string threadId, int progress)
        {
            DownloadProcessChangeEvent?.Invoke(threadId, progress);
        }

        /// <summary>
        /// 更新进度（处理了多少字节）
        /// </summary>
        /// <param name="id">线程序号</param>
        /// <param name="size">文件大小</param>
        /// <param name="progress">读取进度</param>
        /// <param name="position">写入进度</param>
        /// <param name="flowsize">速率</param>
        private void ReportProcess(string id, long size, long progress, long position, long flowsize)
        {
            DownloadChangeEvent?.Invoke(id, size, progress, position, flowsize);
        }

        /// <summary>
        /// 任务完成回执
        /// </summary>
        /// <param name="taskid">任务号</param>
        /// <param name="state">返回状态码</param>
        private void ResponseDownOver(string taskid, int state)
        {
            DownloadOverEvent?.Invoke(taskid, state);
        }

        #endregion 输出日志

        public void Dispose()
        {
            WriteDebugLog("进入销毁方法");
            WriteDebugLog("==================================================");
            if (_ftpClient != null)
            {
                if (_ftpClient.IsConnected)
                {
                    _ftpClient.Disconnect();
                }
                if (!_ftpClient.IsDisposed)
                {
                    _ftpClient.Dispose();
                }
                _ftpClient = null;
            }
        }
    }
}
